package com.base.io_test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * create by yongli on 2020-05-06 22:18
 * 使用非阻塞的模式和注册模式
 * 异步处理，最常见的异步处理
 * Netty in Action有这个例子，但是这时候自己写一变才知道嘛
 */

public class NioServer {

    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress(7070));

            Selector selector = Selector.open(); // 获取选择器

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 对有请求结束干兴趣

            while (true) {
                // 阻塞一下，
//                if(selector.select(1000) == 0){
//                    System.out.println("no interesting things!");
//                    continue;
//                }
                // 有感兴趣的事件才往下走
                try {
                    selector.select();
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 确实我移除了这个key，但是为什么又选择了？
                    // 如果一次没有讲它干掉，每次都干不掉！！
                    iterator.remove(); // 满足条件才移除，不是无条件的

                    if (selectionKey.isAcceptable()) {
                        // iterator.remove();
                        System.out.println("I receiveed a connection");
                        // 对这个channel方法非常熟悉
                        ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                        SocketChannel socketChannel = server.accept();// 此时就能保证获取到一个

                        socketChannel.configureBlocking(false);
                        String att = "read"; // 附加信息很有用，可以告诉使用者信息
                        // 注册
                        // 明白没有，客户端进来的事件是即能读，有能写的事件，这只是注册了单个事件。
                        // 客户端发来的请求就是可读可写的。
                        socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, att);

                    }

                    // 可读的socket就序
                    if (selectionKey.isReadable() && selectionKey.attachment().equals("read")) {
                        //iterator.remove();
                        System.out.println("can write :" + selectionKey.isWritable());
                        // 在注册时已经设置为阻塞模式了
                        System.out.println("I can read it");
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        ByteBuffer buf = ByteBuffer.allocate(1024);

                        // 读取的时候怎么保证读完？
                        int off = 0;
                        while ((off = socketChannel.read(buf)) != -1) {
                            String str = new String(buf.array());
                            System.out.println(str);
                            if (off < 1024) {
                                break;
                            }
                        }
                        // 读完后注册一个写转态
                        String str = "write";
                        // 读完重复注册会报错？
                        socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, str);
                        // selectionKey.attach(str);
                        // socketChannel.close(); // 客户端也会关闭
                    }

                    if (selectionKey.isWritable() && selectionKey.attachment().equals("write")) {
                        // iterator.remove(); //
                        System.out.println("I can write it");
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        // ByteBuffer的用法有问题，它把后面发送出去了
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        buf.put("server have receive your request,".getBytes());
                        buf.put(((String) selectionKey.attachment()).getBytes());
                        buf.flip();
                        while (buf.hasRemaining()) {
//                            socketChannel.write(buf); // 循环知道写完，然后关闭socket
                            System.out.println("write to client");
                            if (socketChannel.write(buf) == 0) {
                                break;
                            }
                        }
                        socketChannel.close(); // 写完就要关流。

                    }
                }
                // 不能在这里关闭serverSocket
            }
            serverSocketChannel.close(); // 最后优雅的关闭 server channel
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
